package com.noassertions.services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.noassertions.data.DAOInterface;
import com.noassertions.domain.Fit;
import com.noassertions.domain.Rider;
import com.noassertions.domain.User;

import edu.wlu.cs.levy.CG.KDTree;
import edu.wlu.cs.levy.CG.KeyDuplicateException;
import edu.wlu.cs.levy.CG.KeySizeException;


/**
 * This is a singleton service that gets started when the webapp is started
 * and provides the interface to create, read, and delete vectors in the kd-tree
 * @author labratmatt
 *
 */

public class KdTreeService {

	protected final Log log = LogFactory.getLog(getClass());
	private DAOInterface dao;
	private static KDTree<Integer> riderKd = new KDTree<Integer>(11);    	
	private static KDTree<Integer> fitKd = new KDTree<Integer>(6);    	

	
    @Autowired
    public KdTreeService(DAOInterface dao) {
        this.dao = dao;
        // build KD-Tree when Spring sets dao
        this.buildRiderKdTree();
        this.buildFitKdTree();
    }  
	
    public void buildRiderKdTree() {
    	
        long start = System.currentTimeMillis();
    	
    	ArrayList<Rider> riderList = dao.getAllRiders();
    	
    	for (Rider rider : riderList) {
    		// Convert values in  rider bean to an array of doubles
    		double[] riderArray = new double[11];
    		riderArray[0] = (double)rider.getFemur();
    		riderArray[1] = (double)rider.getFootlength();
    		riderArray[2] = (double)rider.getForearm();
    		riderArray[3] = (double)rider.getHumerus();
    		riderArray[4] = (double)rider.getInseam();
    		riderArray[5] = (double)rider.getShoulderwidth();
    		riderArray[6] = (double)rider.getSternalnotch();
    		riderArray[7] = (double)rider.getTibia();
    		riderArray[8] = (double)rider.getTotalarm();
    		riderArray[9] = (double)rider.getTotalbodyheight();
    		riderArray[10] = (double)rider.getTrunk();
    		
    		try {
    		    riderKd.insert(riderArray, rider.getUserid());
    		}
    		catch (Exception e) {
    		    System.err.println(e);
    		}
    	}
    	
        long execTime = System.currentTimeMillis() - start;

    	log.info("Rider KD-Tree should now be built. Upon initial construction it has " + riderKd.size() + "nodes. It took " + execTime + " ms to build.");    	
    }
    
    public void buildFitKdTree() {
    	
        long start = System.currentTimeMillis();
    	
    	ArrayList<Fit> fitList = dao.getAllFits();
    	
    	for (Fit fit : fitList) {
    		// Convert values in  fit bean to an array of doubles
    		double[] fitArray = new double[6];
    		fitArray[0] = (double)fit.getBarswidth();
    		fitArray[1] = (double)fit.getCentertobars();
    		fitArray[2] = (double)fit.getCentertosaddle();
    		fitArray[3] = (double)fit.getFloortobars();
    		fitArray[4] = (double)fit.getFloortosaddle();
    		fitArray[5] = (double)fit.getSaddleheight();
    		
    		try {
    		    fitKd.insert(fitArray, fit.getFitid());
    		}
    		catch (Exception e) {
    		    System.err.println(e);
    		}
    	}
    	
        long execTime = System.currentTimeMillis() - start;

    	log.info("Fit KD-Tree should now be built. Upon initial construction it has " + fitKd.size() + " nodes. It took " + execTime + " ms to build.");    	
    }
    

    // Returns an arrayalist of riderids
    public ArrayList getSimilarRiders(Rider rider) throws KeySizeException, IllegalArgumentException {
    	double[] riderArray = new double[11];
    	riderArray[0] = (double)rider.getFemur();
    	riderArray[1] = (double)rider.getFootlength();
    	riderArray[2] = (double)rider.getForearm();
    	riderArray[3] = (double)rider.getHumerus();
    	riderArray[4] = (double)rider.getInseam();
    	riderArray[5] = (double)rider.getShoulderwidth();
    	riderArray[6] = (double)rider.getSternalnotch();
    	riderArray[7] = (double)rider.getTibia();
    	riderArray[8] = (double)rider.getTotalarm();
    	riderArray[9] = (double)rider.getTotalbodyheight();
    	riderArray[10] = (double)rider.getTrunk();

    	List<Integer> nearestRiders = riderKd.nearest(riderArray, 10);
    	log.debug("Found " + nearestRiders.size() + "matches.");

    	// Roll through the array of keys
    	ArrayList riderList = new ArrayList();
    	for (int riderId : nearestRiders) {
    		Rider tempRider = dao.getRiderByID(riderId);
    		ArrayList<User> users = dao.getUserByUserId(riderId);
    		tempRider.setFirstname(users.get(0).getFirstname());
    		tempRider.setLastname(users.get(0).getLastname());
    		riderList.add(tempRider);
    	}
    	// reverse the list since we just reversed it
    	
    	Collections.reverse(riderList);
    	return riderList;
    }
    
    // Returns an arrayalist of riderids
    public ArrayList getSimilarFits(Fit fit) throws KeySizeException, IllegalArgumentException {
		double[] fitArray = new double[6];
		fitArray[0] = (double)fit.getBarswidth();
		fitArray[1] = (double)fit.getCentertobars();
		fitArray[2] = (double)fit.getCentertosaddle();
		fitArray[3] = (double)fit.getFloortobars();
		fitArray[4] = (double)fit.getFloortosaddle();
		fitArray[5] = (double)fit.getSaddleheight();
		
		int targetFitId = fit.getFitid();
		
    	List<Integer> nearestFits = fitKd.nearest(fitArray, 10);
    	log.debug("Found " + nearestFits.size() + " fit matches.");

    	// Roll through the array of keys
    	ArrayList fitList = new ArrayList();
    	for (int fitId : nearestFits) {
    		Fit tempFit = dao.getFitByFitId(fitId);
    		// Don't add user's other fits to similar fits
    		User user = dao.getUserByFitId(fitId);
    		tempFit.setFirstname(user.getFirstname());
    		tempFit.setLastname(user.getLastname());
    		fitList.add(tempFit);
    	}
    	
    	Collections.reverse(fitList);
    	return fitList;
    }
    
    public void addNewRider(Rider rider) throws KeySizeException, KeyDuplicateException {
    	
		// Convert values in  rider bean to an array of doubles
		double[] riderArray = new double[11];
		riderArray[0] = (double)rider.getFemur();
		riderArray[1] = (double)rider.getFootlength();
		riderArray[2] = (double)rider.getForearm();
		riderArray[3] = (double)rider.getHumerus();
		riderArray[4] = (double)rider.getInseam();
		riderArray[5] = (double)rider.getShoulderwidth();
		riderArray[6] = (double)rider.getSternalnotch();
		riderArray[7] = (double)rider.getTibia();
		riderArray[8] = (double)rider.getTotalarm();
		riderArray[9] = (double)rider.getTotalbodyheight();
		riderArray[10] = (double)rider.getTrunk();
		
		riderKd.insert(riderArray, rider.getUserid());
    }
    
    public void addNewFit(Fit fit) throws KeySizeException, KeyDuplicateException {
    	
		double[] fitArray = new double[6];
		fitArray[0] = (double)fit.getBarswidth();
		fitArray[1] = (double)fit.getCentertobars();
		fitArray[2] = (double)fit.getCentertosaddle();
		fitArray[3] = (double)fit.getFloortobars();
		fitArray[4] = (double)fit.getFloortosaddle();
		fitArray[5] = (double)fit.getSaddleheight();
		
		fitKd.insert(fitArray, fit.getFitid());
		
    }
    
    // Returns an arrayalist of riderids
    public ArrayList getSimilarRidersFromRange(Rider lRangeRider, Rider uRangeRider) throws KeySizeException, IllegalArgumentException {

    	double[] lRangeRiderArray = new double[11];
    	lRangeRiderArray[0] = (double)lRangeRider.getFemur();
    	lRangeRiderArray[1] = (double)lRangeRider.getFootlength();
    	lRangeRiderArray[2] = (double)lRangeRider.getForearm();
    	lRangeRiderArray[3] = (double)lRangeRider.getHumerus();
    	lRangeRiderArray[4] = (double)lRangeRider.getInseam();
    	lRangeRiderArray[5] = (double)lRangeRider.getShoulderwidth();
    	lRangeRiderArray[6] = (double)lRangeRider.getSternalnotch();
    	lRangeRiderArray[7] = (double)lRangeRider.getTibia();
    	lRangeRiderArray[8] = (double)lRangeRider.getTotalarm();
    	lRangeRiderArray[9] = (double)lRangeRider.getTotalbodyheight();
    	lRangeRiderArray[10] = (double)lRangeRider.getTrunk();

    	double[] uRangeRiderArray = new double[11];
    	uRangeRiderArray[0] = (double)uRangeRider.getFemur();
    	uRangeRiderArray[1] = (double)uRangeRider.getFootlength();
    	uRangeRiderArray[2] = (double)uRangeRider.getForearm();
    	uRangeRiderArray[3] = (double)uRangeRider.getHumerus();
    	uRangeRiderArray[4] = (double)uRangeRider.getInseam();
    	uRangeRiderArray[5] = (double)uRangeRider.getShoulderwidth();
    	uRangeRiderArray[6] = (double)uRangeRider.getSternalnotch();
    	uRangeRiderArray[7] = (double)uRangeRider.getTibia();
    	uRangeRiderArray[8] = (double)uRangeRider.getTotalarm();
    	uRangeRiderArray[9] = (double)uRangeRider.getTotalbodyheight();
    	uRangeRiderArray[10] = (double)uRangeRider.getTrunk();
    	
    	List<Integer> nearestRiders = riderKd.range(lRangeRiderArray, uRangeRiderArray);
    	log.debug("Found " + nearestRiders.size() + " matches.");

    	// Roll through the array of keys
    	ArrayList riderList = new ArrayList();
    	for (int riderId : nearestRiders) {
    		Rider tempRider = dao.getRiderByID(riderId);
    		ArrayList<User> users = dao.getUserByUserId(riderId);
    		tempRider.setFirstname(users.get(0).getFirstname());
    		tempRider.setLastname(users.get(0).getLastname());
    		riderList.add(tempRider);
    	}
    	// reverse the list since we just reversed it
    	
    	Collections.reverse(riderList);
    	return riderList;
    }
    
    // Returns an arrayalist of fitids
    public ArrayList getSimilarFitsFromRange(Fit lRangeFit, Fit uRangeFit) throws KeySizeException, IllegalArgumentException {

    	double[] lRangeFitArray = new double[6];
    	lRangeFitArray[0] = (double)lRangeFit.getBarswidth();
    	lRangeFitArray[1] = (double)lRangeFit.getCentertobars();
    	lRangeFitArray[2] = (double)lRangeFit.getCentertosaddle();
    	lRangeFitArray[3] = (double)lRangeFit.getFloortobars();
    	lRangeFitArray[4] = (double)lRangeFit.getFloortosaddle();
    	lRangeFitArray[5] = (double)lRangeFit.getSaddleheight();

    	double[] uRangeFitArray = new double[6];
    	uRangeFitArray[0] = (double)uRangeFit.getBarswidth();
    	uRangeFitArray[1] = (double)uRangeFit.getCentertobars();
    	uRangeFitArray[2] = (double)uRangeFit.getCentertosaddle();
    	uRangeFitArray[3] = (double)uRangeFit.getFloortobars();
    	uRangeFitArray[4] = (double)uRangeFit.getFloortosaddle();
    	uRangeFitArray[5] = (double)uRangeFit.getSaddleheight();

    	List<Integer> nearestFits = fitKd.range(lRangeFitArray, uRangeFitArray);
    	log.debug("Found " + nearestFits.size() + " matches.");

    	// Roll through the array of keys
    	ArrayList fitList = new ArrayList();
    	for (int fitId : nearestFits) {
    		Fit tempFit = dao.getFitByFitId(fitId);
    		ArrayList<User> users = dao.getUserByUserId(tempFit.getUserid());
    		tempFit.setFirstname(users.get(0).getFirstname());
    		tempFit.setLastname(users.get(0).getLastname());
    		fitList.add(tempFit);
    	}
    	// reverse the list since we just reversed it
    	
    	Collections.reverse(fitList);
    	return fitList;
    }
}
